#ifndef QTJSONSERIALIZER_HELPERTYPES_H
#define QTJSONSERIALIZER_HELPERTYPES_H

#include <QtCore/qglobal.h>
#include <QtCore/qobject.h>
#include <QtCore/qlist.h>
#include <QtCore/qvector.h>

#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
#include <QtCore/qlinkedlist.h>
#endif

#include <QtCore/qstack.h>
#include <QtCore/qqueue.h>
#include <QtCore/qset.h>
#include <QtCore/qmap.h>
#include <QtCore/qhash.h>
#include <QtCore/qvariant.h>
#include <QtCore/qsharedpointer.h>
#include <QtCore/qpointer.h>
#include <QtCore/qjsonvalue.h>
#include <QtCore/qjsonobject.h>
#include <QtCore/qjsonarray.h>
#include <QtCore/qcborvalue.h>
#include <QtCore/qcbormap.h>
#include <QtCore/qcborarray.h>

#include <type_traits>
#include <tuple>
#include <optional>
#include <variant>

namespace QtJsonSerializer::__private {

	template <class T, class Enable = void>
	struct gadget_helper
	{
		static constexpr bool value = false;
		static inline QJsonValue convert(const QJsonValue& jsonValue) {
			return jsonValue;
		}
	};

	template <class T>
	struct gadget_helper<T, typename T::QtGadgetHelper>
	{
		static constexpr bool value = true;
		static inline QJsonObject convert(const QJsonValue& jsonValue) {
			return jsonValue.toObject();
		}
	};



	template <typename T>
	struct is_serializable : public std::negation<std::is_pointer<T>> {};

	template <typename T>
	struct is_serializable<T*> : public std::disjunction<std::is_base_of<QObject, T>, gadget_helper<T>> {};

	template <typename T>
	struct is_serializable<QSharedPointer<T>> : public std::is_base_of<QObject, T> {};

	template <typename T>
	struct is_serializable<QPointer<T>> : public std::is_base_of<QObject, T> {};

	template <typename T>
	struct is_serializable<QList<T>> : public is_serializable<T> {};

#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
	template <typename T>
	struct is_serializable<QVector<T>> : public is_serializable<T> {};
#endif


#if !defined(QT_NO_LINKED_LIST) && (QT_VERSION < QT_VERSION_CHECK(6, 0, 0))
	template <typename T>
	struct is_serializable<QLinkedList<T>> : public is_serializable<T> {};
#endif

	template <typename T>
	struct is_serializable<QStack<T>> : public is_serializable<T> {};

	template <typename T>
	struct is_serializable<QQueue<T>> : public is_serializable<T> {};

	template <typename TKey, typename TValue>
	struct is_serializable<QMap<TKey, TValue>> : public std::conjunction<is_serializable<TKey>, is_serializable<TValue>> {};

	template <typename TKey, typename TValue>
	struct is_serializable<QMultiMap<TKey, TValue>> : public std::conjunction<is_serializable<TKey>, is_serializable<TValue>> {};

	template <typename TKey, typename TValue>
	struct is_serializable<QHash<TKey, TValue>> : public std::conjunction<is_serializable<TKey>, is_serializable<TValue>> {};

	template <typename TKey, typename TValue>
	struct is_serializable<QMultiHash<TKey, TValue>> : public std::conjunction<is_serializable<TKey>, is_serializable<TValue>> {};

#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
	template <typename T1, typename T2>
	struct is_serializable<QPair<T1, T2>> : public std::conjunction<is_serializable<T1>, is_serializable<T2>> {};
#endif

	template <typename T1, typename T2>
	struct is_serializable<std::pair<T1, T2>> : public std::conjunction<is_serializable<T1>, is_serializable<T2>> {};

	template <typename... TArgs>
	struct is_serializable<std::tuple<TArgs...>> : public std::conjunction<is_serializable<TArgs>...> {};

	template <typename T>
	struct is_serializable<std::optional<T>> : public is_serializable<T> {};

	template <typename... TArgs>
	struct is_serializable<std::variant<TArgs...>> : public std::conjunction<is_serializable<TArgs>...> {};



	template <typename T>
	struct json_type_raw : public std::conditional<gadget_helper<T>::value, QJsonObject, QJsonValue> {};

	template <typename T>
	struct json_type : public json_type_raw<T> {
		static_assert(is_serializable<T>::value, "Type T must be serializable to be used in a generic expression");

		static inline typename json_type_raw<T>::type convert(const QJsonValue& jsonValue) {
			return gadget_helper<T>::convert(jsonValue);
		}
	};

	template <typename T>
	struct json_type<T*> {
		static_assert(is_serializable<T*>::value, "Only QObject deriving classes or gadget pointers can be serialized as pointer");
		using type = QJsonObject;
		using cborType = QCborMap;

		static inline type convert(const QJsonValue& jsonValue) {
			return jsonValue.toObject();
		}
	};

	template <typename T>
	struct json_type<QSharedPointer<T>> {
		static_assert(is_serializable<QSharedPointer<T>>::value, "Only QObject deriving classes can be serialized as shared pointer");
		using type = QJsonObject;
		using cborType = QCborMap;

		static inline type convert(const QJsonValue& jsonValue) {
			return jsonValue.toObject();
		}
	};

	template <typename T>
	struct json_type<QPointer<T>> {
		static_assert(is_serializable<QPointer<T>>::value, "Only QObject deriving classes can be serialized as QPointer");
		using type = QJsonObject;
		using cborType = QCborMap;

		static inline type convert(const QJsonValue& jsonValue) {
			return jsonValue.toObject();
		}
	};

	template <typename T>
	struct json_type<QList<T>> {
		static_assert(is_serializable<QList<T>>::value, "The value type of a QList must be serializable for it to also be serializable");
		using type = QJsonArray;
		using cborType = QCborArray;

		static inline type convert(const QJsonValue& jsonValue) {
			return jsonValue.toArray();
		}
	};

#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
	template <typename T>
	struct json_type<QVector<T>> {
		static_assert(is_serializable<QVector<T>>::value, "The value type of a QVector must be serializable for it to also be serializable");
		using type = QJsonArray;
		using cborType = QCborArray;

		static inline type convert(const QJsonValue& jsonValue) {
			return jsonValue.toArray();
		}
	};
#endif

#if !defined(QT_NO_LINKED_LIST) && (QT_VERSION < QT_VERSION_CHECK(6, 0, 0))
	template <typename T>
	struct json_type<QLinkedList<T>> {
		static_assert(is_serializable<QLinkedList<T>>::value, "The value type of a QLinkedList must be serializable for it to also be serializable");
		using type = QJsonArray;
		using cborType = QCborArray;

		static inline type convert(const QJsonValue& jsonValue) {
			return jsonValue.toArray();
		}
	};
#endif

	template <typename T>
	struct json_type<QStack<T>> {
		static_assert(is_serializable<QStack<T>>::value, "The value type of a QStack must be serializable for it to also be serializable");
		using type = QJsonArray;
		using cborType = QCborArray;

		static inline type convert(const QJsonValue& jsonValue) {
			return jsonValue.toArray();
		}
	};

	template <typename T>
	struct json_type<QQueue<T>> {
		static_assert(is_serializable<QQueue<T>>::value, "The value type of a QQueue must be serializable for it to also be serializable");
		using type = QJsonArray;
		using cborType = QCborArray;

		static inline type convert(const QJsonValue& jsonValue) {
			return jsonValue.toArray();
		}
	};

	template <typename TKey, typename TValue>
	struct json_type<QMap<TKey, TValue>> {
		static_assert(is_serializable<QMap<TKey, TValue>>::value, "The key and value types of a QMap must be serializable for it to also be serializable");
		using type = QJsonObject;
		using cborType = QCborMap;

		static inline type convert(const QJsonValue& jsonValue) {
			return jsonValue.toObject();
		}
	};

	template <typename TKey, typename TValue>
	struct json_type<QMultiMap<TKey, TValue>> {
		static_assert(is_serializable<QMultiMap<TKey, TValue>>::value, "The key and value types of a QMultiMap must be serializable for it to also be serializable");
		using type = QJsonObject;
		using cborType = QCborMap;

		static inline type convert(const QJsonValue& jsonValue) {
			return jsonValue.toObject();
		}
	};

	template <typename TKey, typename TValue>
	struct json_type<QHash<TKey, TValue>> {
		static_assert(is_serializable<QHash<TKey, TValue>>::value, "The key and value types of a QHash must be serializable for it to also be serializable");
		using type = QJsonObject;
		using cborType = QCborMap;

		static inline type convert(const QJsonValue& jsonValue) {
			return jsonValue.toObject();
		}
	};

	template <typename TKey, typename TValue>
	struct json_type<QMultiHash<TKey, TValue>> {
		static_assert(is_serializable<QMultiHash<TKey, TValue>>::value, "The key and value types of a QMultiHash must be serializable for it to also be serializable");
		using type = QJsonObject;
		using cborType = QCborMap;

		static inline type convert(const QJsonValue& jsonValue) {
			return jsonValue.toObject();
		}
	};

#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
	template <typename T1, typename T2>
	struct json_type<QPair<T1, T2>> {
		static_assert(is_serializable<QPair<T1, T2>>::value, "All elements of a QPair must be serializable for it to also be serializable");
		using type = QJsonArray;
		using cborType = QCborArray;

		static inline type convert(const QJsonValue& jsonValue) {
			return jsonValue.toArray();
		}
	};
#endif

	template <typename T1, typename T2>
	struct json_type<std::pair<T1, T2>> {
		static_assert(is_serializable<std::pair<T1, T2>>::value, "All elements of a std::pair must be serializable for it to also be serializable");
		using type = QJsonArray;
		using cborType = QCborArray;

		static inline type convert(const QJsonValue& jsonValue) {
			return jsonValue.toArray();
		}
	};


	template <typename... TArgs>
	struct json_type<std::tuple<TArgs...>> {
		static_assert(is_serializable<std::tuple<TArgs...>>::value, "All elements of a std::tuple must be serializable for it to also be serializable");
		using type = QJsonArray;
		using cborType = QCborArray;

		static inline type convert(const QJsonValue& jsonValue) {
			return jsonValue.toArray();
		}
	};

	template <typename... TArgs>
	struct json_type<std::variant<TArgs...>> {
		static_assert(is_serializable<std::variant<TArgs...>>::value, "All elements of a std::variant must be serializable for it to also be serializable");
		using type = QJsonArray;
		using cborType = QCborArray;

		static inline type convert(const QJsonValue& jsonValue) {
			return jsonValue.toArray();
		}
	};



	template <typename T>
	struct variant_helper {
		static inline QVariant toVariant(const T& data) {
			return QVariant::fromValue<T>(data);
		}
		static inline T fromVariant(const QVariant& data) {
			return data.template value<T>();
		}
	};

	template <>
	struct variant_helper<QVariant> {
		static inline QVariant toVariant(const QVariant& data) {
			return data;
		}
		static inline QVariant fromVariant(const QVariant& data) {
			return data;
		}
	};

}

#endif // QTJSONSERIALIZER_HELPERTYPES_H
